﻿// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using Microsoft.AspNetCore.Hosting;
using System;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;
using Microsoft.AspNetCore;
using Serilog.Events;
using System.Linq;
using Microsoft.Extensions.Configuration;
using System.IO;
using Me.ArkToken.Api.Tools;
using Microsoft.Extensions.DependencyInjection;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.Models;
using IdentityServer4.EntityFramework.Mappers;
using System.Text;

namespace Bit.Art.Sso
{
    /// <summary>
    /// 
    /// </summary>
    public class Program
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="args">The command-line arguments.</param>
        public static void Main(string[] args)
        {
            Console.Title = "交易所单点登录服务器程序";
            
            var seed = args.Contains("/seed");
            if (seed)
            {
                args = args.Except(new[] { "/seed" }).ToArray();
            }
            
            var host = BuildWebHost(args);

            if (seed)
            {
                SeedData.EnsureSeedData(host.Services);
                return;
            }

            if (args.Length > 0 && string.Compare(args[0], "client", true) == 0)
            {
                DoAddClient(args.Skip(1).ToArray(), host.Services);
                return;
            }

            host.Run();
        }

        private static void DoAddClient(string[] args, IServiceProvider serviceProvider)
        {
            var option = ParseClientOption(args);
            if (option != null)
            {
                var errmsg = new StringBuilder();
                if (string.IsNullOrWhiteSpace(option.ClientId)) errmsg.AppendLine("ClientId是必填项，请指明【-i】选项！");
                if (string.IsNullOrWhiteSpace(option.ClientName)) errmsg.AppendLine("ClientName是必填项，请指明【-n】选项！");
                if (option.AllowedScopes == null || option.AllowedScopes.Length == 0)
                    errmsg.AppendLine("AllowedScopes是必填项，请指明【-S】选项！");
                if (option.AllowedGrantTypes == null || option.AllowedGrantTypes.Length == 0)
                    errmsg.AppendLine("AllowedGrantTypes是必填项，请指明【-g】选项！");
                if (option.ClientSecrets == null || option.ClientSecrets.Length == 0)
                    errmsg.AppendLine("ClientSecrets是必填项，请指明【-s】选项！");

                if (errmsg.Length > 0)
                {
                    Console.WriteLine(errmsg.ToString());
                    return;
                }

                errmsg.Clear();
                using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
                {
                    var context = scope.ServiceProvider.GetService<ConfigurationDbContext>();
                    var client = new IdentityServer4.Models.Client
                    {
                        ClientId = option.ClientId.Trim(),
                        ClientName = option.ClientName.Trim(),
                        AllowedScopes = option.AllowedScopes,
                        AllowedGrantTypes = option.AllowedGrantTypes,
                        AllowAccessTokensViaBrowser = option.AllowAccessTokensViaBrowser,
                        RequireConsent = option.RequireConsent
                    };

                    if (option.RedirectUris != null && option.RedirectUris.Length >= 0)
                        client.RedirectUris = option.RedirectUris.Select(u => u.Trim()).ToArray();
                    if (option.PostLogoutRedirectUris != null && option.PostLogoutRedirectUris.Length >= 0)
                        client.PostLogoutRedirectUris = option.PostLogoutRedirectUris.Select(u => u.Trim()).ToArray();
                    if (option.AllowedCorsOrigins != null && option.AllowedCorsOrigins.Length >= 0)
                        client.AllowedCorsOrigins = option.AllowedCorsOrigins.Select(u => u.Trim()).ToArray();
                    if (option.ClientSecrets != null && option.ClientSecrets.Length > 0)
                        client.ClientSecrets = option.ClientSecrets.Select(s => new Secret(s.Trim().Sha256())).ToList();

                    client.AllowOfflineAccess = option.AllowOfflineAccess;

                    if (errmsg.Length > 0)
                    {
                        Console.WriteLine(errmsg.ToString());
                        return;
                    }

                    context.Clients.Add(client.ToEntity());
                    context.SaveChanges();
                }
            }
        }

        private static ClientOption ParseClientOption(string[] args)
        {
            var parser = new XGetopt();
            char c = '\0';
            var opt = new ClientOption();

            while ((c = parser.Getopt(args.Length, args, "ari:n:s:g:bCu:p:o:OS:h")) != '\0')
            {
                switch (c)
                {
                    case 'a':
                        opt.Add = true;
                        break;

                    case 'r':
                        opt.Remove = true;
                        break;

                    case 'i':
                        opt.ClientId = parser.Optarg;
                        break;

                    case 'n':
                        opt.ClientName = parser.Optarg;
                        break;

                    case 's':
                        opt.ClientSecrets = parser.Optarg.Split(',');
                        break;

                    case 'g':
                        opt.AllowedGrantTypes = parser.Optarg.Split(',');
                        break;

                    case 'b':
                        opt.AllowAccessTokensViaBrowser = true;
                        break;

                    case 'C':
                        opt.RequireConsent = true;
                        break;

                    case 'u':
                        opt.RedirectUris = parser.Optarg.Split(',');
                        break;

                    case 'p':
                        opt.PostLogoutRedirectUris = parser.Optarg.Split(',');
                        break;

                    case 'o':
                        opt.AllowedCorsOrigins = parser.Optarg.Split(',');
                        break;

                    case 'O':
                        opt.AllowOfflineAccess = true;
                        break;

                    case 'S':
                        opt.AllowedScopes = parser.Optarg.Split(',');
                        break;

                    case 'h':
                    default:
                        Usage();
                        return null;
                }
            }

            return opt;
        }

        private static void Usage()
        {
            Console.WriteLine("sso client [-a|-r] -i <client id> -n <client name> -s <comma seperated client secrets> " +
                             " -g <comma seperated grant types> [-b|-C] -u <comma seperated redirect uris> " +
                             " -p <comma seperated post logout redirect uris> -o <comma seperated cors origin " +
                             " -S <comma seperated allowed scopes");
        }

        public static IWebHost BuildWebHost(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                .MinimumLevel.Override("System", LogEventLevel.Warning)
                .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
                .Enrich.FromLogContext()
                .WriteTo.File(@"identityserver4_log.txt")
                .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate)
                .CreateLogger();
            
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            return WebHost.CreateDefaultBuilder(args)
                    // 参考文档：https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/?view=aspnetcore-2.1#update-main-method-in-programcs
                    .ConfigureAppConfiguration((hostingContext, config) =>
                    {
                        config.Sources.Clear();
                        config.AddJsonFile("appsettings.json", true, reloadOnChange: false);
                        config.AddJsonFile($"appsettings.{environment}.json", true, reloadOnChange: false);
                        config.AddEnvironmentVariables();
                    })
                    // https://github.com/aspnet/Hosting/issues/1148
                    // ConfigureAppConfiguration和UseConfiguration是不一样的
                    .UseConfiguration(new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
                        .AddJsonFile($"appsettings.{environment}.json", true, reloadOnChange: false)
                        .Build()
                    )
                    .UseStartup<Startup>()
                    .ConfigureLogging(builder =>
                    {
                        builder.ClearProviders();
                        builder.AddSerilog();
                    })
                    .Build();
        }
    }
}